package array_kit

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
)

func IsElementInArray(tarElement interface{}, array interface{}) (bool, error) {
	tarElement_v := reflect.ValueOf(tarElement)
	array_v := reflect.Indirect(reflect.ValueOf(array))
	if !tarElement_v.IsValid() || !array_v.IsValid() {
		return false, errors.New("null value is no valid")
	}
	if array_v.Kind() != reflect.Array && array_v.Kind() != reflect.Slice {
		return false, errors.New("argument array is not array")
	}
	if (isReferenceType(array_v) && array_v.IsNil()) || (isReferenceType(tarElement_v) && tarElement_v.IsNil()) {
		return false, errors.New("null value is not allowed")
	}
	if array_v.Type().Elem() != tarElement_v.Type() {
		return false, errors.New("type is different between targelement and element of array")
	}
	for i := 0; i < array_v.Len(); i++ {
		if reflect.DeepEqual(tarElement, array_v.Index(i).Interface()) {
			return true, nil
		}
	}
	return false, errors.New("targetElement is not in array.")
}

func IsMustElementInArray(tarElement interface{}, array interface{}) bool {
	tarElement_v := reflect.ValueOf(tarElement)
	array_v := reflect.Indirect(reflect.ValueOf(array))
	if !tarElement_v.IsValid() || !array_v.IsValid() {
		panic("null value is no valid")
	}
	if array_v.Kind() != reflect.Array && array_v.Kind() != reflect.Slice {
		panic("argument array is not array")
	}
	if (isReferenceType(array_v) && array_v.IsNil()) || (isReferenceType(tarElement_v) && tarElement_v.IsNil()) {
		panic("null value is not allowed")
	}
	if array_v.Type().Elem() != tarElement_v.Type() {
		panic("type is different between targelement and element of array")
	}
	for i := 0; i < array_v.Len(); i++ {
		if reflect.DeepEqual(tarElement, array_v.Index(i).Interface()) {
			return true
		}
	}
	return false
}

func isReferenceType(value reflect.Value) bool {
	switch value.Kind() {
	case reflect.Ptr, reflect.Chan, reflect.Interface, reflect.Func, reflect.Map, reflect.Slice:
		return true
	default:
		return false
	}
}

func ConvertArrayType(iarray interface{}) []interface{} {
	if !IsArray(iarray) {
		return []interface{}{}
	}
	objV := reflect.Indirect(reflect.ValueOf(iarray))
	result := make([]interface{}, 0, objV.Len())
	for i := 0; i < objV.Len(); i++ {
		result = append(result, objV.Index(i).Interface())
	}
	return result
}

func IsArray(obj interface{}) bool {
	objV := reflect.ValueOf(obj)
	if !objV.IsValid() || (isReferenceType(objV) && objV.IsNil()) {
		return false
	}
	objV = reflect.Indirect(objV)
	if !(objV.Kind() == reflect.Array || objV.Kind() == reflect.Slice) {
		return false
	}
	return true
}

func SplitArray(arraySize int, array []interface{}) [][]interface{} {
	result := [][]interface{}{}
	if array == nil || len(array) == 0 {
		return result
	}
	switch {
	case len(array) <= arraySize:
		return [][]interface{}{array}
	default:
		for len(array) > arraySize {
			temp := array[:arraySize]
			result = append(result, temp)
			array = array[arraySize:]
		}
		return append(result, array)
	}
}

func MergeArray(array1, array2 interface{}) (interface{}, error) {
	switch {
	case InterfaceIsNil(array1) && !InterfaceIsNil(array2):
		return array2, nil
	case InterfaceIsNil(array2) && !InterfaceIsNil(array1):
		return array1, nil
	case InterfaceIsNil(array1) && InterfaceIsNil(array2):
		return nil, errors.New("nil array is not allowed")
	}
	if !IsArray(array1) || !IsArray(array2) {
		return nil, errors.New("type of obj must be array")
	}
	array1V := reflect.Indirect(reflect.ValueOf(array1))
	array2V := reflect.Indirect(reflect.ValueOf(array2))
	if array1V.Type().Elem() != array2V.Type().Elem() {
		return nil, errors.New("the type of array1 must be the same as array2's")
	}
	return reflect.AppendSlice(array1V, array2V).Interface(), nil
}

func RemoveDuplicate(array interface{}) interface{} {
	if InterfaceIsNil(array) || !IsArray(array) {
		return nil
	}
	num := 0
	objV := reflect.Indirect(reflect.ValueOf(array))
	tempArrayV := reflect.MakeSlice(objV.Type(), 0, objV.Len())
	for i := 0; i < objV.Len(); i++ {
		for j := 0; j < objV.Len(); j++ {
			if reflect.DeepEqual(objV.Index(i).Interface(), objV.Index(j).Interface()) {
				num++
			}
			if num > 1 {
				break
			}
		}
		if num == 1 {
			num = 0
			tempArrayV = reflect.Append(tempArrayV, objV.Index(i))
		}
	}
	return tempArrayV.Interface()
}

//DeleteDuplicate keeping one of duplication
func DeleteDuplicate(array interface{}) interface{} {
	if InterfaceIsNil(array) || !IsArray(array) {
		return nil
	}
	num := 0
	objV := reflect.Indirect(reflect.ValueOf(array))
	tempArrayV := reflect.MakeSlice(objV.Type(), 0, objV.Len())
	for i := 0; i < objV.Len(); i++ {
		for j := 0; j < tempArrayV.Len(); j++ {
			if reflect.DeepEqual(objV.Index(i).Interface(), tempArrayV.Index(j).Interface()) {
				num++
			}
			if num == 1 {
				break
			}
		}
		if num == 0 {
			tempArrayV = reflect.Append(tempArrayV, objV.Index(i))
		}
		num = 0
	}
	return tempArrayV.Interface()
}

func DeleteDuplicateStrSlice(s []string) []string {
	sFilter := make([]string, 0, len(s))
	temp := map[string]interface{}{}
	for _, one := range s {
		if _, ok := temp[one]; !ok {
			temp[one] = nil
			sFilter = append(sFilter, one)
		}
	}
	return sFilter
}

func InterfaceIsNil(val interface{}) (flag bool) {
	if !reflect.ValueOf(val).IsValid() {
		return true
	}
	if IsReferenceType(reflect.ValueOf(val)) && reflect.ValueOf(val).IsNil() {
		return true
	}
	return
}

func IsReferenceType(value reflect.Value) bool {
	switch value.Kind() {
	case reflect.Ptr, reflect.Chan, reflect.Interface, reflect.Func, reflect.Map, reflect.Slice:
		return true
	default:
		return false
	}
}

func ConvertToStringArray(iarray []interface{}) []string {
	if InterfaceIsNil(iarray) {
		return []string{}
	}
	convert := make([]string, 0, len(iarray))
	for _, v := range iarray {
		convert = append(convert, ToStringF(v))
	}
	return convert
}

func ToStringF(obj interface{}) string {
	if InterfaceIsNil(obj) {
		return ""
	}
	switch reflect.TypeOf(obj).Kind() {
	default:
		return fmt.Sprintf("%v", obj)
	case reflect.Float32, reflect.Float64:
		return fmt.Sprintf("%g", obj)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
		return fmt.Sprintf("%d", obj)
	}
}

func Intersect(slice1 []interface{}, slice2 []interface{}) []interface{} {
	switch {
	case InterfaceIsNil(slice1) && !InterfaceIsNil(slice2):
		return slice2
	case !InterfaceIsNil(slice1) && InterfaceIsNil(slice2):
		return slice1
	case InterfaceIsNil(slice1) && InterfaceIsNil(slice2):
		return []interface{}{}
	}
	if !IsArray(slice1) || !IsArray(slice2) {
		return []interface{}{}
	}
	m1V := reflect.Indirect(reflect.ValueOf(slice1))
	m2V := reflect.Indirect(reflect.ValueOf(slice2))
	if m1V.Type().Elem() != m2V.Type().Elem() {
		return []interface{}{}
	}
	m := map[interface{}]int{}
	n := reflect.MakeSlice(m1V.Type(), 0, len(slice1))
	for _, v := range slice1 {
		m[v]++
	}
	for _, v := range slice2 {
		times, _ := m[v]
		if times >= 1 {
			n = reflect.Append(n, reflect.ValueOf(v))
		}
	}
	results := make([]interface{}, 0, n.Len())
	for i := 0; i < n.Len(); i++ {
		results = append(results, n.Index(i).Interface())
	}
	return results
}

func Difference(slice1, slice2 []interface{}) []interface{} {
	switch {
	case InterfaceIsNil(slice1) && !InterfaceIsNil(slice2):
		return slice2
	case !InterfaceIsNil(slice1) && InterfaceIsNil(slice2):
		return slice1
	case InterfaceIsNil(slice1) && InterfaceIsNil(slice2):
		return []interface{}{}
	}
	if !IsArray(slice1) || !IsArray(slice2) {
		return []interface{}{}
	}
	m1V := reflect.Indirect(reflect.ValueOf(slice1))
	m2V := reflect.Indirect(reflect.ValueOf(slice2))
	if m1V.Type().Elem() != m2V.Type().Elem() {
		return []interface{}{}
	}
	m := map[interface{}]int{}
	n := reflect.MakeSlice(m1V.Type(), 0, len(slice1))
	inter := Intersect(slice1, slice2)
	for _, v := range inter {
		m[v]++
	}
	for _, value := range slice1 {
		if m[value] == 0 {
			n = reflect.Append(n, reflect.ValueOf(value))
		}
	}
	for _, v := range slice2 {
		if m[v] == 0 {
			n = reflect.Append(n, reflect.ValueOf(v))
		}
	}
	results := make([]interface{}, 0, n.Len())
	for i := 0; i < n.Len(); i++ {
		results = append(results, n.Index(i).Interface())
	}
	return results
}

func ToStrArray(a []interface{}) []string {
	r := []string{}
	for _, one := range a {
		r = append(r, ToStringF(one))
	}
	return r
}

func ToInt64Array(a []interface{}) []int64 {
	r := []int64{}
	for _, one := range a {
		r = append(r, ToInt64(one))
	}
	return r
}

func ToInt64(val interface{}) int64 {
	switch val.(type) {
	case float64:
		return int64(val.(float64))
	case int:
		return int64(val.(int))
	case int32:
		return int64(val.(int32))
	case int64:
		return val.(int64)
	case string:
		return ParseInt64String(val.(string))
	}
	return 0
}

func ParseInt(b string, defInt int) int {
	id, err := strconv.Atoi(b)
	if err != nil {
		return defInt
	}
	return id
}

func ParseInt64String(b string) int64 {
	id, _ := strconv.ParseInt(b, 10, 64)
	return id
}
